with GNAT.Regexp;

with GNAT.Sockets;
with GNAT.IO;
use GNAT;

with Ada.Streams;

with Ada.Text_IO.Text_Streams;
use Ada;

with Percentages;

with Headers_Parser;

--
-- HTTP_Get
--
--    Parent: Website_Retrieval
--
--    Implementation of website retrieval using HTTP GET request.
--

package body Website_Retrieval.HTTP_Get is

   pragma Warnings (Off, "formal parameter ""Website_Retrieval_Obj"" is not referenced");

   --
   -- separate http headers from content
   --
   --    instantiate outside of procedure in order to accommodate large Strings
   --

   package Headers_Parser_Pkg is new Headers_Parser(

         Max_Input => Website_Retrieval.Max_Download );

   use Headers_Parser_Pkg;


   --
   -- use content-length child package to gleam value of the content-length
   --    header
   --


   --
   -- Retrieve
   --
   --    wrapper for Retrieve, see below
   --
   --    provide convenience String parameters, defaults for file output
   --

   not overriding
   procedure Retrieve( Website_Retrieval_Obj : in out Website_Retrieval_Object;

                       Host_Address_Str : in String;

                       File_Name_Str    : in String )
   is

      Host_Addr_Parm : Host_Address_Sub_Type;

      File_Name_Parm : File_Name_Sub_Type;

   begin

      Host_Address_Type.Append( Host_Addr_Parm, Host_Address_Str );

      File_Name_Type.Append( File_Name_Parm, File_Name_Str );

      Website_Retrieval_Obj.Retrieve( Host_Address   => Host_Addr_Parm,
                           Output_Str_Arg => Website_Retrieval.Output_Str,
                           Output         => Write_To'Value("File"),
                           File_Name_Arg  => File_Name_Parm );

   end Retrieve;


   --
   -- Retrieve
   --
   --    Download a URL using HTTP.
   --
   --    Accepts canonical URL or IP and any path or query string.
   --

   overriding
   procedure Retrieve( Website_Retrieval_Obj : in out Website_Retrieval_Object;

                       Host_Address   : in Host_Address_Sub_Type;

                       Output         : in Write_To := Write_To'Value(
                           "String_Variable");

                       File_Name_Arg  : in File_Name_Type.Bounded_String :=
                           Empty_File_Name;

                       Output_Str_Arg : in out Output_String_Type
                           .Bounded_String;

                       Verbose        : in Boolean := false )

   is

      --
      -- constants
      --

      -- http request carriage return and line feed

      NL : constant String := ASCII.CR & ASCII.LF;

      PERCENTAGE_TEXT_MAX_LENGTH : constant := 20;

      Host_Entry : constant Sockets.Host_Entry_Type

        := Sockets.Get_Host_By_Name(

            Parse_Host( Host_Address ) );

      -- regular expresion validation of http[s]

      Protocol_RegEx : constant Regexp.Regexp := Regexp.Compile(

            "http(s)?://", Case_Sensitive => false );

      --
      -- bound strings
      --

      -- formatted HTTP request String

      package HTTP_Request_Type is new Limit_Str.Generic_Bounded_Length(

            HTTP_REQUEST_MAX_LENGTH );

      HTTP_Request : HTTP_Request_Type.Bounded_String;

      -- percentage calculation

      package Percentage_Text_Type is new Limit_Str.Generic_Bounded_Length(

            PERCENTAGE_TEXT_MAX_LENGTH);

      Percentage_Text : Percentage_Text_Type.Bounded_String;

      --
      -- fixed point decimals
      --

      -- percentage calculation results as fixed point decimal

      type Percent_Results_Type is delta 0.1 digits 15;

      Percent_Results : Percent_Results_Type;

      -- percentage quantity arguments

      type Percent_Quantity_Type is delta 0.01 digits 15;

      --
      -- Percentage: calculate percentage.
      --
      --       generic fixed point decimal for quantity arguments
      --       generic fixed point decimal for results of calculation
      --
      --       Allows custom precision in percentage calculation.
      --

      package Percentages_Pkg is new Percentages(

            Qty_Type => Percent_Quantity_Type,

            Percentage_Type => Percent_Results_Type );

      --
      -- Sockets, Streams
      --

      Address : Sockets.Sock_Addr_Type;
      Socket  : Sockets.Socket_Type;
      Channel : Sockets.Stream_Access;

      Data    : Ada.Streams.Stream_Element_Array (1..1024);
      Size    : Ada.Streams.Stream_Element_Offset;

      --
      -- File output
      --
      --       For writing website contents to a file.
      --

      Stream_File_Out : Text_IO.File_Type;


   begin

      if( Output = Write_To'Value("File") ) then

         if( File_Name_Arg'Size < 1 ) then

            raise File_Name_Invalid;   -- empty file name

         end if;

         Text_IO.Create( File => Stream_File_Out,
                         Mode => Text_IO.Out_File,
                         Name => File_Name_Type.To_String( File_Name_Arg ) );

      end if;


      if( Host_Address'Size < HOST_MIN_LENGTH ) then
         raise Host_Address_Invalid_Length;

      end if;

      -- ensure http or https

      if( not Regexp.Match(

                Parse_Host( Host_Address,
                            Address_Portion'Value("Protocol") ),

                            Protocol_RegEx ))
      then

         raise Host_Unsupported_Protocol;

      end if;

      -- set address to host server specified, parsed from website address arg

      Address.Addr := GNAT.Sockets.Addresses(Host_Entry, 1);

      -- format HTTP request String

      HTTP_Request_Type.Append( HTTP_Request, "GET /" );
      HTTP_Request_Type.Append( HTTP_Request,

                Parse_Host( Host_Address, Address_Portion'Value("Path") ));

      HTTP_Request_Type.Append( HTTP_Request, " HTTP/1.1" );
      HTTP_Request_Type.Append( HTTP_Request, NL );
      HTTP_Request_Type.Append( HTTP_Request, "Host: " );
      HTTP_Request_Type.Append( HTTP_Request,

                Parse_Host( Host_Address, Address_Portion'Value("Host") ));

      HTTP_Request_Type.Append( HTTP_Request, NL );
      HTTP_Request_Type.Append( HTTP_Request, "Connection: close" );
      HTTP_Request_Type.Append( HTTP_Request, NL );
      HTTP_Request_Type.Append( HTTP_Request, NL );

      -- optionally display generic argument for buffer, HTTP request

      if( Verbose ) then

         IO.Put_Line( "Download maximum: "
                      & Integer'Image(Max_Download)
                      & NL
                      & " HTTP Request: "
                      & HTTP_Request_Type.To_String( HTTP_Request ) );

      end if;

      -- write the HTTP request to the socket

      Address.Port := 80;
      Sockets.Create_Socket (Socket);
      Sockets.Connect_Socket (Socket, Address );

      Channel := Sockets.Stream (Socket);
      String'Write (Channel, HTTP_Request_Type.To_String( HTTP_Request ) );

      -- receive the request into Stream

      Sockets.Receive_Socket(Socket,Data,Size);

      -- iterate and receive, converting integer byte representation into
      --    Character, placing into String for return

      for i in 1 .. Size loop

         Output_String_Type.Append( Output_Str_Arg, Character'Val(Data(i)));

      end loop;

      if( Output_String_Type.Length( Output_Str_Arg ) < 1 ) then

         raise Download_Empty;

      end if;

      if( Output = Write_To'Value("File") ) then

         String'Write( Text_IO.Text_Streams.Stream( Stream_File_Out ),

                       Headers_Parser_Pkg.Parse_Content(

                           Output_String_Type.To_String( Output_Str_Arg )));

         Text_IO.Close( File => Stream_File_Out );

      end if;

      -- optionally display % of space used

      if( Verbose ) then

         -- calculate percentage of space used as permitted generic maximum

         Percent_Results := Percentages_Pkg.Percentage(

               Percent_Quantity_Type(Size),

               Percent_Quantity_Type(Max_Download) );

         Percentage_Text_Type.Append( Percentage_Text,
                                      Percent_Results_Type'Image(

               Percent_Results_Type(100) - Percent_Results ) );

         IO.Put_Line( Output_String_Type.To_String( Output_Str_Arg )
                      & Ascii.LF
                      & Ascii.LF
                      & "Download Buffer: "
                      & Percentage_Text_Type.To_String(Percentage_Text)
                      & " % free"
                      & NL );

      end if;


   end Retrieve;

   pragma Warnings (On, "formal parameter ""Website_Retrieval_Obj"" is not referenced");


end Website_Retrieval.HTTP_Get;